Skip to content

fix(pm): stop dispatch-gates requiring a .changeset/*.md population it cannot own - #15277

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-15255-changeset-population-control
Sep 4, 2026
Merged

fix(pm): stop dispatch-gates requiring a .changeset/*.md population it cannot own#15277
os-zhuang merged 2 commits into
mainfrom
claude/issue-15255-changeset-population-control

Conversation

@hotlong

@hotlong hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15255

dispatch-gates.mjs's self-test asserted changesetPop.length >= 100 against the live
working tree. That population belongs to the release cycle, not to this tool: a changesets
version pass consumes all of it. So the control reds on the Version Packages PR — a
required context, Lint & Repo Gates, on the one PR whose merge is the release
trigger — and then on main for as long as it takes 100 fresh changesets to accumulate.

⛔ This PR does not merge, queue or auto-merge the Version Packages PR #11336, and pushes
nothing to changeset-release/main. It lands the gate repair on main as an ordinary PR.

A correction to the card's premise, and why it changes the fix

The card reads the population on the release PR as 0. Measured instead: it is 1.
.changeset/README.md survives a version pass, so b8573e843 carries exactly
README.md + config.json (read via the contents API on that ref). That is why CI
reported 1 of 1353 case(s) failed and not three — the other two corpus assertions are
satisfied by a one-file population.

This matters for the shape: a guard written as "skip when the population is 0" would
still have been red on the very tree it was written for. The size claim has to go, not
just be gated.

What replaces it

  • The three corpus assertions run whenever the population is non-empty, so they are real at
    1, at 3 and at 865. The vacuity the >= 100 was guarding (every over nothing,
    0 === 0) is closed by construction: they no longer run over an empty set at all.
  • An empty population reports NOT MEASURED, out loud — its own line, a reason a
    reader can check (git ls-files '.changeset/'), and a tally repeated on the verdict
    line. It is never pushed into cases, where it would be one more .
  • What a gate can require is the specimen's home: .changeset/config.json. While it
    is tracked, an empty population is this repo mid-cycle and the specimen is resting; when
    it goes, the specimen has rotted and somebody must pick a new one. That is the
    distinction "large" could not draw, and it is what stops a silent skip from letting the
    specimen decay unnoticed.
  • The second live-population site — the residue block's hintReachesTree — takes the
    same exit. Left unguarded it would have kept producing the release-blocking red after the
    measured site stopped.

Measured readings

Each is a real run of node scripts/pm/dispatch-gates.mjs --self-test, populations mutated
in an isolated scratch worktree (own index; this branch's tree was never mutated).

population script verdict exit
865 (this branch, pre-merge) fixed ✓ 1363 cases pass. 0
869 (this branch, post-merge, head 3c4728cb5) fixed ✓ 1363 cases pass. 0
3 (main for days after a release) fixed ✓ 1363 cases pass. — 0 skipped, corpus assertions ran 0
1 (the real release-PR tree) pre-fix ✗ 1 of 1353 case(s) failed. — reproduces the CI red exactly 1
1 (the real release-PR tree) fixed ✓ 1363 cases pass. — 0 skipped 0
0 (README.md removed too) fixed ✓ 1359 cases pass. ⊘ 2 subject(s) NOT MEASURED … 0

The pre-fix leg is the reverse verification: same tree, same command, only the script
swapped, and it fails on exactly the case the card names.

Gates

Derived on the final commit 3c4728cb5 with node scripts/pm/dispatch-gates.mjs (no paths
passed — the tool reads its own change set). All green:

check:pm-dispatch-gates (the self-test, 1363) · check:nul-bytes · check:declared-population-live ·
check:ratchet-remedy-authority · check:watch-hint-literal · check:parse-guard · check:entry-guard ·
check:agent-test-spelling · check:cli-command-ids · check:cross-package-test-inputs ·
check:pm-half-states · check-self-test-wired · check-whole-set-label-write ·
check-closing-keyword-parity · check-comment-mask-corpus · measure-self-test-floor

ESLint was run over the whole repo, not narrowed: eslint . --no-inline-config --format json
5877 files, 0 errors, 0 warnings.

Two readings that are not verdicts on this change:

  • check:bash32-floor fails 8 of 153 cases here — and fails identically on pristine
    main in a separate checkout
    . A host-bash artifact of this macOS seat, not this diff.
  • check-closing-keyword-parity and check-comment-mask-corpus first exited on their own
    stated prerequisite (no node_modules; the former exits 3 and says so explicitly). That
    is NOT MEASURED, not a finding. Both were re-run green after pnpm install.

Seat declaration

scripts/pm/os-verify-lock.sh printed, verbatim, on every heavy run of this card:

os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 140s (2m20s) · declare it in the PR body · node scripts/pm/dispatch-gates.mjs --self-test

So the wall-clock figures above are shared-box readings, not quiet-machine promises.

scripts/pm/** releases nothing from any package, so this carries the skip-changeset
label rather than a .changeset/*.md entry — which would have been a small irony here.

Generated by Claude Code

hotlong and others added 2 commits September 4, 2026 15:45
… it cannot own

The self-test's live-specimen block asserted `changesetPop.length >= 100`
against the real working tree. That population is owned by the release cycle,
not by this tool: a changesets version pass consumes all of it, so the control
reds on the Version Packages PR — a required context, on the one PR whose merge
IS the release — and then on `main` for the days it takes 100 fresh changesets
to accumulate.

The control was guarding something real: the two assertions under it pass
vacuously over an empty population (`every` on nothing, `0 === 0`). That vacuity
is now closed by construction instead of by a size claim — the corpus assertions
run only where there is a corpus, and where there is not, the run says NOT
MEASURED out loud, names why, and carries the tally on its verdict line. A
silent skip would let the specimen rot, which is what the `>= 100` was reaching
for.

What a gate can require is the specimen's HOME: `.changeset/config.json`. While
it is tracked, an empty population is this repo mid-cycle; when it goes, the
specimen is rotted and somebody must pick a new one. That is the distinction the
count could not draw.

The second live-population site — the residue block's `hintReachesTree` — takes
the same exit, so the red does not survive the repair at the site that was
measured.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 4, 2026 08:27
@os-zhuang
os-zhuang enabled auto-merge September 4, 2026 08:27
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit ee434a3 Sep 4, 2026
36 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-15255-changeset-population-control branch September 4, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants